<!DOCTYPE html>
<html>

<head>
    
    <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer">

<meta name="description" content="A new Hugo site.">
<title>
    基于Websocket的消息传输系统 - dashjay
</title>



<link rel="shortcut icon" href="/sam.ico">








<link rel="stylesheet" href="/css/main.min.4321edce0de245f4b1d32680d89ac669940fe5dec17e1791c721d9a0954987b4.css" integrity="sha256-QyHtzg3iRfSx0yaA2JrGaZQP5d7BfheRxyHZoJVJh7Q=" crossorigin="anonymous" media="screen">





<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Didact+Gothic">

<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="http://dashjay.gitee.io/tn.png"/>

<meta name="twitter:title" content="基于Websocket的消息传输系统"/>
<meta name="twitter:description" content="丑话说前面，本人就是LJ本科生，水平没多高，大多数代码源自其他项目，比如一个叫leaf的游戏框架。
leafgithub.com
但是作为普通的穷苦人民，在上面催你的时候，很难静下新来，慢慢的打开vscode去一行一行的看，比如你现在可能就是在火急火燎的想在知乎上寻求一个解决方案。
先说一下我的使用场景。我的场景是这样：
用户会登陆软件后，需要一些认证方法，连接上socket，然后会进行高速传输，比如消息、群消息、交易拍卖、其他等等一系列消息，主体就是用户，客体是其他用户或者服务器。
架构 我现对整个项目架构描述一下
先简述一下Hub的设计，总的一个Hub包含Client这样一个map用来储存连接信息，然后每个连接有一个Reader和一个Write来从客户端读取和写入客户端。
按照图中标志，按实际执行顺序来讲
①. 客户端向服务端发起Socket连接，（服务器识别到这是Socket连接之后有个Upgrade操作），这里不太明白的可以去看看Socket连接的过程，并不复杂。
③. Reader收到的第一条消息，是注册消息，一般又客户端OnConnect的时候发送，一般会携带认证信息进行身份认证。
②. 认证成功之后服务器应该返回用户类似于JWT或者SessionCookie类的东西，或者没有也没关系，因为每次重新创建连接都要认证，我用JWT是为了做访问控制。
 如果没有其他异常：就会一直处在①、②、①、②&hellip;&hellip;这样的收发状态。直到&hellip;&hellip;.
 ④. 注销，一旦客户端退出或者网络中断等异常，就会出现断开。
好了讲了整个东西架构，让我们来看看怎么实现的把，showcode时间到了。
实现 首先，让我们来看看hub的结构吧，说结构还真的就是结构
// 整个项目的交流系统，由clients和各种不同的chan组成， type Hub struct { Clients map[*Client]bool // 储存保持连接的用户  UserToClient map[string]*Client // 获得用户和其连接的对应关系  Message chan []byte // 广播消息队列  Register chan *Client // 发起注册请求的队列  Unregister chan *Client // 断开连接的队列 } 注释讲的比较清楚了，我中间使用UserToClient这个map来方便找到我们的Client而已，
 注意，Client我们存的是指针，因此在这里并没有冗余，我只是为了方便找用户，而且不用再遍历。
 这里大家有没有注意到一个好玩的东西叫做chan ，这个东西在C/C&#43;&#43;里叫做pipe，但是我个人觉得在go里爽的不止那么一点点，有这个东西，我的思路清晰多了。
 我在这里插入一个操作系统中生产者消费者的例子，如果觉得没啥好玩的可以跳过。
 package main import ( &#34;fmt&#34; &#34;time&#34; ) func produce(p chan&lt;- int) { for i := 0; i &lt; 1000; i&#43;&#43; { p &lt;- i fmt."/>

<meta property="og:title" content="基于Websocket的消息传输系统" />
<meta property="og:description" content="丑话说前面，本人就是LJ本科生，水平没多高，大多数代码源自其他项目，比如一个叫leaf的游戏框架。
leafgithub.com
但是作为普通的穷苦人民，在上面催你的时候，很难静下新来，慢慢的打开vscode去一行一行的看，比如你现在可能就是在火急火燎的想在知乎上寻求一个解决方案。
先说一下我的使用场景。我的场景是这样：
用户会登陆软件后，需要一些认证方法，连接上socket，然后会进行高速传输，比如消息、群消息、交易拍卖、其他等等一系列消息，主体就是用户，客体是其他用户或者服务器。
架构 我现对整个项目架构描述一下
先简述一下Hub的设计，总的一个Hub包含Client这样一个map用来储存连接信息，然后每个连接有一个Reader和一个Write来从客户端读取和写入客户端。
按照图中标志，按实际执行顺序来讲
①. 客户端向服务端发起Socket连接，（服务器识别到这是Socket连接之后有个Upgrade操作），这里不太明白的可以去看看Socket连接的过程，并不复杂。
③. Reader收到的第一条消息，是注册消息，一般又客户端OnConnect的时候发送，一般会携带认证信息进行身份认证。
②. 认证成功之后服务器应该返回用户类似于JWT或者SessionCookie类的东西，或者没有也没关系，因为每次重新创建连接都要认证，我用JWT是为了做访问控制。
 如果没有其他异常：就会一直处在①、②、①、②&hellip;&hellip;这样的收发状态。直到&hellip;&hellip;.
 ④. 注销，一旦客户端退出或者网络中断等异常，就会出现断开。
好了讲了整个东西架构，让我们来看看怎么实现的把，showcode时间到了。
实现 首先，让我们来看看hub的结构吧，说结构还真的就是结构
// 整个项目的交流系统，由clients和各种不同的chan组成， type Hub struct { Clients map[*Client]bool // 储存保持连接的用户  UserToClient map[string]*Client // 获得用户和其连接的对应关系  Message chan []byte // 广播消息队列  Register chan *Client // 发起注册请求的队列  Unregister chan *Client // 断开连接的队列 } 注释讲的比较清楚了，我中间使用UserToClient这个map来方便找到我们的Client而已，
 注意，Client我们存的是指针，因此在这里并没有冗余，我只是为了方便找用户，而且不用再遍历。
 这里大家有没有注意到一个好玩的东西叫做chan ，这个东西在C/C&#43;&#43;里叫做pipe，但是我个人觉得在go里爽的不止那么一点点，有这个东西，我的思路清晰多了。
 我在这里插入一个操作系统中生产者消费者的例子，如果觉得没啥好玩的可以跳过。
 package main import ( &#34;fmt&#34; &#34;time&#34; ) func produce(p chan&lt;- int) { for i := 0; i &lt; 1000; i&#43;&#43; { p &lt;- i fmt." />
<meta property="og:type" content="article" />
<meta property="og:url" content="http://dashjay.gitee.io/posts/websocket/" />
<meta property="og:image" content="http://dashjay.gitee.io/tn.png"/>
<meta property="article:published_time" content="2019-12-03T00:00:00+00:00" />
<meta property="article:modified_time" content="2019-12-03T00:00:00+00:00" /><meta property="og:site_name" content="Call me Sam" />


    

    
    
    
    <title>
        
        基于Websocket的消息传输系统
        
    </title>
</head>

<body>
    
    
    <header class="wrap flex-container">
        <h1>基于Websocket的消息传输系统</h1>
    </header>
    
    <main class="wrap">
        
<div class="flex-container">
    <aside role="complementary">
        Tue Dec 03, 2019 &#183; 718 words
        <div class="tag-container">
            
        </div>
    </aside>
    <hr />
    <article role="article">
        <p>丑话说前面，本人就是LJ本科生，水平没多高，大多数代码源自其他项目，比如一个叫leaf的游戏框架。</p>
<p><a href="https://link.zhihu.com/?target=https%3A//github.com/name5566/leaf">leafgithub.com</a></p>
<p>但是作为普通的穷苦人民，在上面催你的时候，很难静下新来，慢慢的打开vscode去一行一行的看，比如你现在可能就是在火急火燎的想在知乎上寻求一个解决方案。</p>
<p>先说一下我的使用场景。我的场景是这样：</p>
<p>用户会登陆软件后，需要一些认证方法，连接上socket，然后会进行高速传输，比如消息、群消息、交易拍卖、其他等等一系列消息，主体就是用户，客体是其他用户或者服务器。</p>
<h1 id="架构"><strong>架构</strong></h1>
<p>我现对整个项目架构描述一下</p>
<p><img src="https://pic3.zhimg.com/80/v2-f064e3332e066c5ec3a41331ffe0e302_hd.jpg" alt="img"></p>
<p>先简述一下Hub的设计，总的一个Hub包含Client这样一个map用来储存连接信息，然后每个连接有一个Reader和一个Write来从客户端读取和写入客户端。</p>
<p>按照图中标志，按实际执行顺序来讲</p>
<p>①. 客户端向服务端发起Socket连接，（服务器识别到这是Socket连接之后有个Upgrade操作），这里不太明白的可以去看看Socket连接的过程，并不复杂。</p>
<p>③. Reader收到的第一条消息，是注册消息，一般又客户端OnConnect的时候发送，一般会携带认证信息进行身份认证。</p>
<p>②. 认证成功之后服务器应该返回用户类似于JWT或者SessionCookie类的东西，或者没有也没关系，因为每次重新创建连接都要认证，我用JWT是为了做访问控制。</p>
<blockquote>
<p>如果没有其他异常：就会一直处在①、②、①、②&hellip;&hellip;这样的收发状态。直到&hellip;&hellip;.</p>
</blockquote>
<p>④. 注销，一旦客户端退出或者网络中断等异常，就会出现断开。</p>
<p>好了讲了整个东西架构，让我们来看看怎么实现的把，showcode时间到了。</p>
<h1 id="实现"><strong>实现</strong></h1>
<p>首先，让我们来看看hub的结构吧，说结构还真的就是结构</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// 整个项目的交流系统，由clients和各种不同的chan组成，
</span><span style="color:#75715e"></span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Hub</span> <span style="color:#66d9ef">struct</span> {
    <span style="color:#a6e22e">Clients</span>      <span style="color:#66d9ef">map</span>[<span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>]<span style="color:#66d9ef">bool</span>   <span style="color:#75715e">// 储存保持连接的用户
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">UserToClient</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span> <span style="color:#75715e">// 获得用户和其连接的对应关系
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">Message</span>      <span style="color:#66d9ef">chan</span> []<span style="color:#66d9ef">byte</span>        <span style="color:#75715e">// 广播消息队列
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">Register</span>     <span style="color:#66d9ef">chan</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>       <span style="color:#75715e">// 发起注册请求的队列
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">Unregister</span>   <span style="color:#66d9ef">chan</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>       <span style="color:#75715e">// 断开连接的队列
</span><span style="color:#75715e"></span>}
</code></pre></div><p>注释讲的比较清楚了，我中间使用UserToClient这个map来方便找到我们的Client而已，</p>
<blockquote>
<p>注意，Client我们存的是指针，因此在这里并没有冗余，我只是为了方便找用户，而且不用再遍历。</p>
</blockquote>
<p>这里大家有没有注意到一个好玩的东西叫做<code>chan</code> ，这个东西在C/C++里叫做pipe，但是我个人觉得在go里爽的不止那么一点点，有这个东西，我的思路清晰多了。</p>
<blockquote>
<p>我在这里插入一个操作系统中生产者消费者的例子，如果觉得没啥好玩的可以跳过。</p>
</blockquote>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>

<span style="color:#f92672">import</span> (
    <span style="color:#e6db74">&#34;fmt&#34;</span>
    <span style="color:#e6db74">&#34;time&#34;</span>
)

<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">produce</span>(<span style="color:#a6e22e">p</span> <span style="color:#66d9ef">chan</span><span style="color:#f92672">&lt;-</span> <span style="color:#66d9ef">int</span>) {
    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">1000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
        <span style="color:#a6e22e">p</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">i</span>
        <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;send:&#34;</span>, <span style="color:#a6e22e">i</span>)
    }
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">consumer</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">&lt;-</span><span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>) {
    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">1000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
        <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">c</span>
        <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;receive:&#34;</span>, <span style="color:#a6e22e">v</span>)
    }
}
<span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
    <span style="color:#a6e22e">ch</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">chan</span> <span style="color:#66d9ef">int</span>, <span style="color:#ae81ff">5</span>)
    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">produce</span>(<span style="color:#a6e22e">ch</span>)
    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">consumer</span>(<span style="color:#a6e22e">ch</span>)
    <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">10000</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
}
</code></pre></div><p>就这么个东西，golang 给我们提供了一个类似于管道的东西，目的是进行进程间通信。我们先假装自己不知道goroutine这个东西，我们就可以认为 go produce(ch) 就相当于手动执行了一个程序，传入了我们事先 make 的一个长度（缓冲长度）为 5 的chan <code>ch :=make(chanint,5)</code> 这个管道有点像。</p>
<p>你还没放学，你妈妈给你做了5个烧饼，你回来就吃了一个 ，然后妈妈问你还要不要，你说要，然后管道里又多了一个生产者生产的烧饼，就是这样一个过程，说白了有点像个队列，具体我就没有看了。</p>
<p>============操作系统课结束=============</p>
<p>那么我们在本次项目当中也会使用这种 <code>chan</code> ，功能就是这样：</p>
<blockquote>
<p>再补充一个关键词 阻塞（block） 就是当程序执行到 v &lt;- chan 然后这个 chan 里没东西的时候，程序就会阻塞。那么好我们来看一下怎么用上面的那个Hub的结构。</p>
</blockquote>
<p>忘了说，这个Hub是单例，也就是说整个代码里只运行一份。因此一个Hub要写完整个项目的逻辑，那么我就从hub的创建开始</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewHub</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">Hub</span> {
    <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Hub</span>{
        <span style="color:#a6e22e">Message</span>:      make(<span style="color:#66d9ef">chan</span> []<span style="color:#66d9ef">byte</span>),        <span style="color:#75715e">// 消息队列
</span><span style="color:#75715e"></span>        <span style="color:#a6e22e">Register</span>:     make(<span style="color:#66d9ef">chan</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>),       <span style="color:#75715e">// 等待创建连接的用户
</span><span style="color:#75715e"></span>        <span style="color:#a6e22e">Unregister</span>:   make(<span style="color:#66d9ef">chan</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>),       <span style="color:#75715e">// 等待退出断开连接的用户
</span><span style="color:#75715e"></span>        <span style="color:#a6e22e">Clients</span>:      make(<span style="color:#66d9ef">map</span>[<span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>]<span style="color:#66d9ef">bool</span>),   <span style="color:#75715e">// 客户数组
</span><span style="color:#75715e"></span>        <span style="color:#a6e22e">UserToClient</span>: make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>), <span style="color:#75715e">//建立用户名和客户端唯的唯一通道
</span><span style="color:#75715e"></span>    }
}
</code></pre></div><p>main函数中运行hub并且run起来</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// 创建端系统的hub
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">hub</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">starlight</span>.<span style="color:#a6e22e">NewHub</span>()
    <span style="color:#75715e">// 运行hub
</span><span style="color:#75715e"></span>    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">hub</span>.<span style="color:#a6e22e">Run</span>()
</code></pre></div><p>我的天，我给项目起了个名字，叫星光，太羞耻了。</p>
<p>当同时操控好几 chan 的时候golang官方推荐一种处理方式使用语句 select，以下案例来自官方。</p>
<blockquote>
<p>If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the &ldquo;select&rdquo; statement blocks until at least one of the communications can proceed.</p>
</blockquote>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">//select基本用法
</span><span style="color:#75715e"></span><span style="color:#66d9ef">select</span> {
<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">chan1</span>:
<span style="color:#75715e">// 如果chan1成功读到数据，则进行该case处理语句
</span><span style="color:#75715e"></span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">chan2</span> <span style="color:#f92672">&lt;-</span> <span style="color:#ae81ff">1</span>:
<span style="color:#75715e">// 如果成功向chan2写入数据，则进行该case处理语句
</span><span style="color:#75715e"></span><span style="color:#66d9ef">default</span>:
<span style="color:#75715e">// 如果上面都没有成功，则进入default处理流程
</span></code></pre></div><p>这样就如何使用hub就很明朗了，你只需要将自己游戏或者软件里的逻辑每个业务需要使用 chan 的让他循环跑起来，像这样</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// Run 跑起来，完成和用户保持连接，注册，注销用户等功能，控制消息队列
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">h</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Hub</span>) <span style="color:#a6e22e">Run</span>() {
    <span style="color:#66d9ef">for</span> {
        <span style="color:#66d9ef">select</span> {

        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Register</span>: <span style="color:#75715e">// 用户创建新的连接
</span><span style="color:#75715e"></span>            {
                <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Clients</span>[<span style="color:#a6e22e">client</span>] = <span style="color:#66d9ef">true</span>
            }

        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Unregister</span>: <span style="color:#75715e">// 用户断开连接
</span><span style="color:#75715e"></span>            {
                <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Clients</span>[<span style="color:#a6e22e">client</span>]; <span style="color:#a6e22e">ok</span> {
                    delete(<span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Clients</span>, <span style="color:#a6e22e">client</span>)
                    close(<span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Send</span>)
                }
                delete(<span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">UserToClient</span>, <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Info</span>.<span style="color:#a6e22e">UserID</span>)
            }
        <span style="color:#75715e">// 这个是全局消息 每个人都会收到
</span><span style="color:#75715e"></span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">message</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Message</span>:
            {
                <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">client</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Clients</span> {
                    <span style="color:#66d9ef">select</span> {
                    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Send</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">message</span>:
                    <span style="color:#66d9ef">default</span>:
                        close(<span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">Send</span>)
                        delete(<span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">Clients</span>, <span style="color:#a6e22e">client</span>)
                    }
                }
            }
        }
    }
}
</code></pre></div><p>第一个 <code>case client := &lt;- h.Register</code>也就是从注册队列中取一个出来执行注册逻辑，下面是注销逻辑，记得用了哪些东西必须删除，然后下面那个Message是大喇叭，所有客户端都会收到消息，写好这个东西，你的加特林就已经转起来了，虽然还没有一发子弹。</p>
<p>那我们来看一下简单逻辑。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// 监听消息
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/ws&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
        <span style="color:#a6e22e">wshandler</span>.<span style="color:#a6e22e">ServeWs</span>(<span style="color:#a6e22e">hub</span>, <span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>)
    })
</code></pre></div><p>这里我使用的是官方http库，如果要使用beego或者iris等框架，应该有对应的 FromStd 的handler，我记得 iris 的handler是一个只有 <code>iris.Context</code>的函数，但是应该有<code>iris.FronStd</code>这样的函数，总之就是一个 Handler 嘛 。然后我们来看一下ServeWs怎么写的。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#75715e">// serveWs handles websocket requests from the peer.
</span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ServeWs</span>(<span style="color:#a6e22e">hub</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">starlight</span>.<span style="color:#a6e22e">Hub</span>, <span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
    <span style="color:#75715e">//完成之后将http请求升级成socket连接
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">upgrader</span>.<span style="color:#a6e22e">Upgrade</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>, <span style="color:#66d9ef">nil</span>)
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
        <span style="color:#66d9ef">return</span>
    }
    <span style="color:#75715e">// 初始化该用户
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">starlight</span>.<span style="color:#a6e22e">Client</span>{<span style="color:#a6e22e">Hub</span>: <span style="color:#a6e22e">hub</span>, <span style="color:#a6e22e">Conn</span>: <span style="color:#a6e22e">conn</span>, <span style="color:#a6e22e">Send</span>: make(<span style="color:#66d9ef">chan</span> []<span style="color:#66d9ef">byte</span>, <span style="color:#ae81ff">256</span>)}
    <span style="color:#75715e">// 连接注册
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">hub</span>.<span style="color:#a6e22e">Register</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">c</span>

    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">ReadPump</span>()
    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">WritePump</span>()
}
</code></pre></div><p>我用升级这个词可能不专业，但是官方库的函数名称就叫做 <code>Upgrade</code>，然后中间用到了一个upgrader这个是官方写法，应该算是一个中间件，写法如下。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">upgrader</span> = <span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">Upgrader</span>{
    <span style="color:#a6e22e">ReadBufferSize</span>:  <span style="color:#ae81ff">1024</span>,
    <span style="color:#a6e22e">WriteBufferSize</span>: <span style="color:#ae81ff">1024</span>,
    <span style="color:#a6e22e">CheckOrigin</span>:     <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">bool</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span> },
}
</code></pre></div><p>现在幻想一下，http服务也跑起来了，hub也跑起来了，每一个用户只要请求，服务器的/ws，他发起的Socket连接就会被 upgrade 成一个WebSocket连接，并且被Client持有，他的指针会被推入注册的的队列中，你再会看一下之前的注册逻辑，其实就是把Client这个map的对应项设置为true。到现在为止客户端已经可以和用户保持连接了。但是还不能收发数据。根据更前方的框架图主要在<code>go c.ReadPump() go c.WritePump()</code>这两个函数上，他们在处理，和用户交互的逻辑。 我们一样的进来看看这两个函数怎么写的。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>) <span style="color:#a6e22e">ReadPump</span>() {
    <span style="color:#75715e">// 函数返回后执行内容
</span><span style="color:#75715e"></span>    <span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
        <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Hub</span>.<span style="color:#a6e22e">Unregister</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">c</span>
        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">Close</span>()
    }()

    <span style="color:#75715e">// 设置读限制
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">SetReadLimit</span>(<span style="color:#a6e22e">maxMessageSize</span>)
    <span style="color:#75715e">//read超时时间
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">SetReadDeadline</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">pongWait</span>))
    <span style="color:#75715e">// 超时操作
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">SetPongHandler</span>(<span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">error</span> {
        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">SetReadDeadline</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">pongWait</span>))
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
    })

    <span style="color:#75715e">// 第一个消息就是处理注册
</span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">OnLogin</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
        <span style="color:#66d9ef">return</span>
    }
        <span style="color:#66d9ef">for</span> { <span style="color:#f92672">......</span>..
<span style="color:#f92672">............</span>.
</code></pre></div><p>无非就是一些配置，然后再循环外面执行OnConnect的操作，我们来看看 Login 咋写的我都快忘了</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>) <span style="color:#a6e22e">OnLogin</span>() <span style="color:#66d9ef">error</span> {

    <span style="color:#75715e">// 接收用户的消息头
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">message</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">ReadMessage</span>()
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
    }
        <span style="color:#75715e">// 回复注册
</span><span style="color:#75715e"></span>    <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Send</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">message</span>
}
</code></pre></div><p>我这里没有做什么特殊处理，也就是将用户发过来的消息原封不动发回去就算是成功注册了，但是在业务逻辑当中你可能需要比如，认证用户，并且加入某种权限组之类的操作，一类操作。在这里我就不再详细说。</p>
<p>你们有没有注意到<code>ReadPump()</code>函数开始写了一段defer</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
        <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Hub</span>.<span style="color:#a6e22e">Unregister</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">c</span>
        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">Close</span>()
    }()
</code></pre></div><p>这个东西作用就是一旦用户出现任何异常，立马<code>return</code>立马关闭他的连接，防止出现任何异常，你可以写上其他操作，不过一般 注销逻辑都放在hub下面的for select那写了。</p>
<p>ReadPump后面for循环内的逻辑就比较简单了，</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">for</span> {

        <span style="color:#75715e">// 读取消息
</span><span style="color:#75715e"></span>        <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">message</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">ReadMessage</span>()

        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">IsUnexpectedCloseError</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">CloseGoingAway</span>, <span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">CloseAbnormalClosure</span>) {
                <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;error: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
            }
            <span style="color:#66d9ef">break</span>
        }
        <span style="color:#a6e22e">mess</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">Message</span>{}

        <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>(<span style="color:#a6e22e">message</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">mess</span>)
        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
            <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
            <span style="color:#66d9ef">continue</span>
        }
<span style="color:#f92672">......</span>..
</code></pre></div><p>我还是不全部粘贴了，我的垃圾代码实在是太长了，主要我的业务逻辑比较扯淡，复杂。</p>
<p>你会发现这样一个问题，你从c是可以取到这个client的标识的，并且可与取到hub，那么爽的来了，当某c说要发消息给另一个c`的时候，我们就直接粗暴解决问题</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Hub</span>.<span style="color:#a6e22e">UserToClient</span>[<span style="color:#a6e22e">mess</span>.<span style="color:#a6e22e">ToUserID</span>].<span style="color:#a6e22e">Send</span> <span style="color:#f92672">&lt;-</span> <span style="color:#a6e22e">res</span>
</code></pre></div><p>简直爽到不能在爽，你的加特林一直在疯狂转（其实是阻塞的），你只要把消息用 &lt;-这个操作符推进去，消息就会自动通过<code>WritePump</code>发出去。</p>
<p>那么这个项目算不算完成了，暂时没有，WritePump也看一眼，这个东西是一劳永逸的函数写完一次就不用动了。</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-go" data-lang="go"><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>) <span style="color:#a6e22e">WritePump</span>() {
    <span style="color:#a6e22e">ticker</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">NewTicker</span>(<span style="color:#a6e22e">pingPeriod</span>)
    <span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
        <span style="color:#a6e22e">ticker</span>.<span style="color:#a6e22e">Stop</span>()
        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">Close</span>()
    }()

    <span style="color:#75715e">// 发送消息的队列
</span><span style="color:#75715e"></span>    <span style="color:#66d9ef">for</span> {
        <span style="color:#66d9ef">select</span> {
        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">message</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Send</span>:
            <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">SetWriteDeadline</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">writeWait</span>))
            <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
                <span style="color:#75715e">// The hub closed the channel.
</span><span style="color:#75715e"></span>                <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">WriteMessage</span>(<span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">CloseMessage</span>, []<span style="color:#66d9ef">byte</span>{})
                <span style="color:#66d9ef">return</span>
            }
            <span style="color:#75715e">// TODO:这里是一个发送内容需要指定的Writer
</span><span style="color:#75715e"></span>            <span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">NextWriter</span>(<span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">TextMessage</span>)
            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
                <span style="color:#66d9ef">return</span>
            }
            <span style="color:#75715e">// 发送取出的第一条消息
</span><span style="color:#75715e"></span>            <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">message</span>)

            <span style="color:#75715e">// 将队列中的消息全都发送出来
</span><span style="color:#75715e"></span>            <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> len(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Send</span>)
            <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#a6e22e">n</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {

                <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Send</span>)
            }

            <span style="color:#75715e">// 差错
</span><span style="color:#75715e"></span>            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Close</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
                <span style="color:#66d9ef">return</span>
            }
        <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ticker</span>.<span style="color:#a6e22e">C</span>:
            <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">SetWriteDeadline</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">writeWait</span>))
            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Conn</span>.<span style="color:#a6e22e">WriteMessage</span>(<span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">PingMessage</span>, <span style="color:#66d9ef">nil</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
                <span style="color:#66d9ef">return</span>
            }
        }
    }
}
</code></pre></div><p>我都写了注释了，这个里面这个ticker我也没看是什么东西，但是从别人那儿把代码Copy过来的。到此为止这个项目就结束了。</p>
<p>再整理一遍，</p>
<p>1、 初始化一个Hub，并且运行起来</p>
<p>2、接收用户请求，upgrade成socket连接并且交给hub持有。</p>
<p>3、运行<code>Readpump()</code>，<code>WritePump()</code>来从客户端读取消息，并且写入消息。</p>
<p>4、每个队列各司其职，完美解决消息传输业务。</p>
<p>有人想看我代码么？有人看在留个言，我再整理整理，主要我的代码里面框架都被业务逻辑淹没了，比较垃圾。</p>
<p>就酱，最近人在山东，太冷了，冷影响写代码了。</p>
<p>加油，各位代码狗子们，我已经写了一年半的代码了，进展很快。现在上手什么语言过渡都很快，很适合写垃圾业务逻辑代码，啥时候能给我个清净的研究的时间啊，哎。</p>

    </article>
</div>


        
<nav role="navigation" class="flex-container bottom-menu">
    
<hr />
<p>


    
        <a href="/posts">back</a>
        
            &#183;
        
    

    
        
            <a href="/posts">我的文章</a>
        
    
    
        
            &#183; 
            <a href="/gallery">画册</a>
        
            &#183; 
            <a href="/portfolio">珍贵时光</a>
        
            &#183; 
            <a href="/about">关于我</a>
        
    
    &#183; 
    <a href="/">
        main
    </a>

</p>
</nav>

    </main>
    
    <footer class="flex-container footer">I will code for a long time. Would you?</footer>
    
    
</body>

</html>